// Copyright (c) Microsoft Corporation. 
// Licensed under the MIT license.

#include "LODRecipeCustomization.h"
#include "LODRecipe.h"

#include "PropertyHandle.h"
#include "DetailLayoutBuilder.h"
#include "DetailCategoryBuilder.h"
#include "DetailWidgetRow.h"
#include "PropertyCustomizationHelpers.h"
#include "IDetailChildrenBuilder.h"
#include "IDetailPropertyRow.h"

#include "Widgets/Input/SButton.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Layout/SSeparator.h"
#include "Widgets/Input/SSpinBox.h"
#include "SPerPlatformPropertiesWidget.h"
#include "Engine/StaticMesh.h"
#include "Engine/SkeletalMesh.h"

#define LOCTEXT_NAMESPACE "LODRecipeEditor"

TSharedRef<IDetailCustomization> FLODRecipeDetailsCustomization::MakeInstance()
{
	return MakeShareable(new FLODRecipeDetailsCustomization());
}

void FLODRecipeDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailLayout)
{
	Builder = &DetailLayout;

	TArray<TWeakObjectPtr<UObject>> Objects;
	DetailLayout.GetObjectsBeingCustomized(Objects);
	if (Objects.Num() != 1)
	{
		return;
	}
	TWeakObjectPtr<ULODRecipe> LODRecipeBeingCustomized = Cast< ULODRecipe >(Objects[0].Get());

	// Cache handles
	StaticMeshesHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(ULODRecipe, StaticMeshes));
	SkeletalMeshesHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(ULODRecipe, SkeletalMeshes));
	PipelineSettingsHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(ULODRecipe, PerLODPipelineSettings));
	MiscSettingsHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(ULODRecipe, PerLODMiscSettings));
	NumLODsHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(ULODRecipe, NumLODs));
	NumStreamedLODsHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(ULODRecipe, NumStreamedLODs));

	auto SetOnNumMeshChanged = [this](TSharedPtr<IPropertyHandleArray> MeshArray)
	{
		if (MeshArray.IsValid())
		{
			FSimpleDelegate RefreshDelegate = FSimpleDelegate::CreateLambda([this]() { Builder->ForceRefreshDetails(); });
			MeshArray->SetOnNumElementsChanged(RefreshDelegate);
		}
			
	};

	SetOnNumMeshChanged(StaticMeshesHandle->AsArray());
	SetOnNumMeshChanged(SkeletalMeshesHandle->AsArray());	
	NumLODsHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FLODRecipeDetailsCustomization::OnNumLODsChanged));

	UpdateLODSettingsVisibiility();

	IDetailCategoryBuilder& AssetsCategory = DetailLayout.EditCategory("Assets", FText::GetEmpty(), ECategoryPriority::Default);

	TSharedRef<FDetailArrayBuilder> StaticMeshesBuilder = MakeShareable(new FDetailArrayBuilder(StaticMeshesHandle.ToSharedRef()));
	StaticMeshesBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FLODRecipeDetailsCustomization::GenerateStaticMeshesWidget, LODRecipeBeingCustomized.Get()));
	AssetsCategory.AddCustomBuilder(StaticMeshesBuilder);

	TSharedRef<FDetailArrayBuilder> SkeletalMeshesBuilder = MakeShareable(new FDetailArrayBuilder(SkeletalMeshesHandle.ToSharedRef()));
	SkeletalMeshesBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FLODRecipeDetailsCustomization::GenerateSkeletalMeshesWidget, LODRecipeBeingCustomized.Get()));
	AssetsCategory.AddCustomBuilder(SkeletalMeshesBuilder);

	IDetailCategoryBuilder& LevelOfDetailCategory = DetailLayout.EditCategory("LevelOfDetail", FText::GetEmpty(), ECategoryPriority::Default);
	LevelOfDetailCategory.AddProperty(NumLODsHandle);

	auto CustomizePerLODArrayProperty = [this, &LevelOfDetailCategory, &LODRecipeBeingCustomized, &DetailLayout](TSharedPtr<IPropertyHandle> PerLODArrayProperty)
	{
		IDetailPropertyRow* PerLODArrayRow = DetailLayout.EditDefaultProperty(PerLODArrayProperty);
		if (PerLODArrayRow)
			PerLODArrayRow->ShouldAutoExpand(true);

		TSharedRef<FDetailArrayBuilder> PerLODArrayPropertyBuilder = MakeShareable(new FDetailArrayBuilder(PerLODArrayProperty.ToSharedRef(), true, true, false));
		PerLODArrayPropertyBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FLODRecipeDetailsCustomization::GeneratePerLODPipelineWidget, LODRecipeBeingCustomized.Get()));
		LevelOfDetailCategory.AddCustomBuilder(PerLODArrayPropertyBuilder);
	};

	CustomizePerLODArrayProperty(PipelineSettingsHandle);
	CustomizePerLODArrayProperty(MiscSettingsHandle);

	FText NumStreamedLODs = LOCTEXT("LODRecipe_NumStreamedLODs", "Number of Streamed LODs");
	LevelOfDetailCategory.AddCustomRow(NumStreamedLODs)
		.NameContent()
		[
			SNew(STextBlock)
			.Font(IDetailLayoutBuilder::GetDetailFont())
			.Text(NumStreamedLODs)
		]
		.ValueContent()
		[
			SNew(SHorizontalBox)
			+ SHorizontalBox::Slot()
			.AutoWidth()
			[
				SNew(SVerticalBox)
				+ SVerticalBox::Slot()
				.AutoHeight()
				[
					SNew(SPerPlatformPropertiesWidget)
					.IsEnabled(true)
					.OnGenerateWidget(this, &FLODRecipeDetailsCustomization::GenerateStreamedLODsWidget, LODRecipeBeingCustomized.Get())
					.OnAddPlatform(this, &FLODRecipeDetailsCustomization::AddStreamedLODsPlatform, LODRecipeBeingCustomized.Get())
					.OnRemovePlatform(this, &FLODRecipeDetailsCustomization::RemoveStreamedLODsPlatform, LODRecipeBeingCustomized.Get())
					.PlatformOverrideNames(this, &FLODRecipeDetailsCustomization::GetStreamedLODsPlatformOverrideNames, LODRecipeBeingCustomized.Get())
				]
				+ SVerticalBox::Slot()
				.AutoHeight()
				[
					SNew(SButton)
					.Text(LOCTEXT("ApplyStreamedLODs", "Apply Streamed LODs"))
					.ToolTipText(LOCTEXT("ApplyStreamedLODsToolTip", "Apply Streamed LODs to all assets"))
					.OnClicked(FOnClicked::CreateSP(this, &FLODRecipeDetailsCustomization::ApplyStreamedLODs, LODRecipeBeingCustomized.Get()))
				]	
			]
		];

}

FLODRecipeDetailsCustomization::FLODRecipeDetailsCustomization()
	: Builder(nullptr)
{
	SettingsVisibility.Init(EVisibility::Collapsed, MAX_MESH_LOD_COUNT);
}

void FLODRecipeDetailsCustomization::GenerateStaticMeshesWidget(TSharedRef<IPropertyHandle> PropertyHandle, int32 ArrayIndex, IDetailChildrenBuilder& ChildrenBuilder, ULODRecipe* LODRecipe)
{
	TSharedRef<SWidget> RemoveButton = PropertyCustomizationHelpers::MakeRemoveButton(
		FSimpleDelegate::CreateSP(this, &FLODRecipeDetailsCustomization::OnRemoveStaticMesh, LODRecipe, ArrayIndex));

	GenerateMeshArrayItem(PropertyHandle, ChildrenBuilder, RemoveButton);
}

void FLODRecipeDetailsCustomization::GenerateSkeletalMeshesWidget(TSharedRef<IPropertyHandle> PropertyHandle, int32 ArrayIndex, IDetailChildrenBuilder& ChildrenBuilder, ULODRecipe* LODRecipe)
{
	TSharedRef<SWidget> RemoveButton = PropertyCustomizationHelpers::MakeRemoveButton(
		FSimpleDelegate::CreateSP(this, &FLODRecipeDetailsCustomization::OnRemoveSkeletalMesh, LODRecipe, ArrayIndex));

	GenerateMeshArrayItem(PropertyHandle, ChildrenBuilder, RemoveButton);
}

void FLODRecipeDetailsCustomization::GeneratePerLODPipelineWidget(TSharedRef<IPropertyHandle> PropertyHandle, int32 ArrayIndex, IDetailChildrenBuilder& ChildrenBuilder, ULODRecipe* /*LODRecipe*/)
{
	IDetailPropertyRow& PerLODPipelineItem = ChildrenBuilder.AddProperty(PropertyHandle);
	
	// Hide SeparateOverlappingCharts since it is decided automatically based on if vertex colors exists or not
	TSharedPtr<IPropertyHandle> SeparateChartProperty = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FChartAggregatorSettings, SeparateOverlappingCharts));
	if (SeparateChartProperty.IsValid())
	{
		SeparateChartProperty->MarkHiddenByCustomization();
	}

	const FText PerLODPipelineHeader = FText::Format(LOCTEXT("PerLODPipelineHeader", "LOD {0}"), FText::AsNumber(ArrayIndex));

	if(ArrayIndex == 0)
		PerLODPipelineItem.IsEnabled(false);

	PerLODPipelineItem.ShowPropertyButtons(false);
	PerLODPipelineItem.Visibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FLODRecipeDetailsCustomization::IsLODSettingsVisibile, ArrayIndex)));
	PerLODPipelineItem.CustomWidget(true)
		.NameContent()
		[
			SNew(SHorizontalBox)
			+ SHorizontalBox::Slot()
			[
				SNew(STextBlock)
				.Font(IDetailLayoutBuilder::GetDetailFont())
				.Text(PerLODPipelineHeader)
			]
		]
		.ValueContent()
		.MaxDesiredWidth(TOptional<float>())
		[
			PropertyHandle->CreatePropertyValueWidget()
		];
}

void FLODRecipeDetailsCustomization::GenerateMeshArrayItem(TSharedRef<IPropertyHandle> PropertyHandle, IDetailChildrenBuilder& ChildrenBuilder, TSharedRef<SWidget> RemoveButton)
{
	IDetailPropertyRow& StaticMeshRow = ChildrenBuilder.AddProperty(PropertyHandle);
	StaticMeshRow.ShowPropertyButtons(false);

	StaticMeshRow.CustomWidget(false)
		.ValueContent()
		.MaxDesiredWidth(TOptional<float>())
		[
			SNew(SHorizontalBox)
			+ SHorizontalBox::Slot()
			.FillWidth(0.8f)
			[
				PropertyHandle->CreatePropertyValueWidget()
			]
			+ SHorizontalBox::Slot()
			.AutoWidth()
			//.Padding(FMargin(0.0f, 4.0f, 0.0f, 0.0f))
			[
				SNew(SVerticalBox)
				+ SVerticalBox::Slot()
				.VAlign(VAlign_Center)
				[
					RemoveButton
				]
			]
		];
}

TSharedRef<SWidget> FLODRecipeDetailsCustomization::GenerateStreamedLODsWidget(FName PlatformGroupName, ULODRecipe* LODRecipe) const
{
	return SNew(SSpinBox<int32>)
		.Font(IDetailLayoutBuilder::GetDetailFont())
		.Value(this, &FLODRecipeDetailsCustomization::GetNumStreamedLODs, PlatformGroupName, LODRecipe)
		.OnValueChanged(const_cast<FLODRecipeDetailsCustomization*>(this), &FLODRecipeDetailsCustomization::OnNumStreamedLODsChanged, PlatformGroupName, LODRecipe)
		.OnValueCommitted(const_cast<FLODRecipeDetailsCustomization*>(this), &FLODRecipeDetailsCustomization::OnNumStreamedLODsCommited, PlatformGroupName, LODRecipe)
		.MinValue(-1)
		.MaxValue(MAX_STATIC_MESH_LODS)
		.IsEnabled(true);
}

int32 FLODRecipeDetailsCustomization::GetNumStreamedLODs(FName Platform, ULODRecipe* LODRecipe) const
{
	check(LODRecipe)
	int32* PlatformValue = (Platform == NAME_None) ? nullptr : LODRecipe->NumStreamedLODs.PerPlatform.Find(Platform);
	return (PlatformValue != nullptr) ? *PlatformValue : LODRecipe->NumStreamedLODs.Default;
}

void FLODRecipeDetailsCustomization::OnNumStreamedLODsChanged(int32 NewValue, FName Platform, ULODRecipe* LODRecipe)
{
	SetNumStreamedLODsValue(NewValue, Platform, LODRecipe);
}

void FLODRecipeDetailsCustomization::OnNumStreamedLODsCommited(int32 InValue, ETextCommit::Type /*CommitInfo*/, FName Platform, ULODRecipe* LODRecipe)
{
	SetNumStreamedLODsValue(InValue, Platform, LODRecipe);
}

void FLODRecipeDetailsCustomization::SetNumStreamedLODsValue(int32 NewValue, FName Platform, ULODRecipe* LODRecipe)
{
	NewValue = FMath::Clamp<int32>(NewValue, -1, MAX_STATIC_MESH_LODS);
	if (Platform == NAME_None)
	{
		LODRecipe->NumStreamedLODs.Default = NewValue;
	}
	else
	{
		int32* PlatformValue = LODRecipe->NumStreamedLODs.PerPlatform.Find(Platform);
		if (PlatformValue != nullptr)
		{
			*PlatformValue = NewValue;
		}
	}
}

bool FLODRecipeDetailsCustomization::AddStreamedLODsPlatform(FName PlatformGroupName, ULODRecipe* LODRecipe)
{
	check(LODRecipe);
	if (LODRecipe->NumStreamedLODs.PerPlatform.Find(PlatformGroupName) == nullptr)
	{
		int32 Value = LODRecipe->NumStreamedLODs.Default;
		LODRecipe->NumStreamedLODs.PerPlatform.Add(PlatformGroupName, Value);
		OnNumStreamedLODsChanged(Value, PlatformGroupName, LODRecipe);
		return true;
	}
	return false;
}

bool FLODRecipeDetailsCustomization::RemoveStreamedLODsPlatform(FName PlatformGroupName, ULODRecipe* LODRecipe)
{
	check(LODRecipe);
	if (LODRecipe->NumStreamedLODs.PerPlatform.Remove(PlatformGroupName) != 0)
	{
		OnNumStreamedLODsChanged(LODRecipe->NumStreamedLODs.Default, PlatformGroupName, LODRecipe);
		return true;
	}
	return false;
}

TArray<FName> FLODRecipeDetailsCustomization::GetStreamedLODsPlatformOverrideNames(ULODRecipe* LODRecipe) const
{
	check(LODRecipe);
	TArray<FName> PlatformNames;
	LODRecipe->NumStreamedLODs.PerPlatform.GenerateKeyArray(PlatformNames);
	PlatformNames.Sort(FNameLexicalLess());
	return PlatformNames;
}

void FLODRecipeDetailsCustomization::OnRemoveStaticMesh(ULODRecipe* LODRecipe, const int32 MeshIndex)
{
	const TArray<TSoftObjectPtr<UStaticMesh>>& StaticMeshes = LODRecipe->GetStaticMeshes();
	if (MeshIndex < StaticMeshes.Num())
	{
		LODRecipe->RemoveMesh(StaticMeshes[MeshIndex]);
		Builder->ForceRefreshDetails();
	}
}

void FLODRecipeDetailsCustomization::OnRemoveSkeletalMesh(ULODRecipe* LODRecipe, const int32 MeshIndex)
{
	const TArray<TSoftObjectPtr<USkeletalMesh>>& SkeletalMeshes = LODRecipe->GetSkeletalMeshes();
	if (MeshIndex < SkeletalMeshes.Num())
	{
		LODRecipe->RemoveMesh(SkeletalMeshes[MeshIndex]);
		Builder->ForceRefreshDetails();
	}
}

void FLODRecipeDetailsCustomization::UpdateLODSettingsVisibiility()
{
	int32 NumLODs = 0;
	NumLODsHandle->GetValue(NumLODs);
	for (int32 LODIndex = 0; LODIndex < SettingsVisibility.Num(); ++LODIndex)
	{
		if (LODIndex < NumLODs)
			SettingsVisibility[LODIndex] = EVisibility::Visible;
		else
			SettingsVisibility[LODIndex] = EVisibility::Collapsed;
	}
}

EVisibility FLODRecipeDetailsCustomization::IsLODSettingsVisibile(const int32 LODIndex) const
{
	return SettingsVisibility[LODIndex];
}

void FLODRecipeDetailsCustomization::OnNumLODsChanged()
{
	UpdateLODSettingsVisibiility();
}

FReply FLODRecipeDetailsCustomization::ApplyStreamedLODs(ULODRecipe* LODRecipe)
{
	LODRecipe->ApplyStreamedLODsToMeshes(true);
	return FReply::Handled();
}
#undef LOCTEXT_NAMESPACE
